<?php
/**
 *	Klasa obsługująca pobieranie danych z bazy
 *	
 *	Rozszerzenie klasy mysqli.
 */
 defined("BASEDIR") or die();
 
 class Application_Model_DataBase extends mysqli implements Application_Interface_DataBaseModel
 {
	protected static $instance, $connection;													// Instancja klasy oraz identyfikator połaczenia
	protected $squery, $type, $table, $fields, $values, $set, $where, $order, $group, $limit;	// dane do zapytania
	protected $tableColumns;																	// lista pól w wybranej tabeli
	protected $queryResult, $returnResult;														// wynik zapytania;
	
/**
 * Konstruktor
 *
 * Możliwy do wywołania tylko z wewnątrz klasy
 * W przypadku braku pliku konfiguracyjnego lub błędów w danych wyrzuca odpowiedni wyjątek
 */
	protected function __construct()
	{
		if(!defined("DBHOST")) throw new Exception("Błąd połączenia z bazą danych : HOST", 400);
		if(!defined("DBPORT")) throw new Exception("Błąd połączenia z bazą danych : PORT", 401);
		if(!defined("DBUSER")) throw new Exception("Błąd połączenia z bazą danych : USER", 402);
		if(!defined("DBPASS")) throw new Exception("Błąd połączenia z bazą danych : PASS", 403);
		if(!defined("DBNAME")) throw new Exception("Błąd połączenia z bazą danych : BASE", 404);
		else
		{
			ob_start();
			$this->connection = parent::__construct(constant("DBHOST"), constant("DBUSER"), constant("DBPASS"), constant("DBNAME"), constant("DBPORT"));
			//parent::query("SET CHARACTER SET 'utf8_general_ci'");
			//parent::query("SET NAMES 'latin1'");
			ob_clean();

		}
	}
/**
 * Pobranie instancji obiektu klasy
 *
 * Pobiera istniejącą instancje obiektu klasy, lub w przypadku jej braku towrzy nową
 *
 * @return self Zwraca obiekt instancji klasy
 */
	public static function getInstance()
	{
		if(!self::$instance instanceof self)
			self::$instance = new self;
		return self::$instance;
	}
/**
 * Wybieranie tablicy na której zostaną wykonane operacje
 *
 * Ustawia nazwę tabeli na której zostanie wykonane zapytanie, usuwa zmienne użyte w poprzednich zapytaniach
 * jak parametr należy podać nazwę tabeli oraz typ zapytania (SELECT, INSERT, UPDATE, DELETE)
 *
 * @param string $table Nazwa tablicy na której będą wykonywane operacje
 * @param string $type Rodzaj akcji która zostanie wykonana (SELECT, INSERT, UPDATE, DELETE)
 * @return this Zwraca instancję klasy, można używać Method Chaining
 */
	public function getTable($table, $type = "SELECT")
	{
		$this->table = (defined("DBPREFIX") ? constant("DBPREFIX") : null).$table;
		$this->table2 = null;
		$this->type = strtoupper($type);
		$this->getTableColumns();
		$this->where = null;
		$this->fields = null;
		$this->values = null;
		$this->set = null;
		$this->order = null;
		$this->group = null;
		$this->limit = null;
		$this->squery = null;
		$this->queryResult = null;
		$this->returnResult = null;
		
		return $this;
	}
/**
 * Wybieranie drugiej tablicy na której zostaną wykonane operacje
 *
 * Ustawia nazwę drugiej tabeli na której zostanie wykonane zapytanie, następuje złączenie typu NATURAL JOIN
 * jak parametr należy podać nazwę tabeli
 *
 * @param string $table Nazwa tablicy na której będą wykonywane operacje
 * @return this Zwraca instancję klasy, można używać Method Chaining
 */
	public function andTable($table)
	{
		$this->table2 = (defined("DBPREFIX") ? constant("DBPREFIX") : null).$table;
		$this->getTableColumns(2);
		
		return $this;
	}
/** Metoda setFields
 * Ustawia nazwy pól które zostaną użyte w zapytaniu
 *
 * jak parametr należy podać nazwy pól które zostaną wykorzystane w zapytaniu
 *
 * @param string $fields Nazwy pól podane jako string, rozdzielone przecinkami(pole1, pole2, ... )
 * @return this Zwraca instancję klasy, można używać Method Chaining
 */
	public function setFields($fields)
	{
		$temp = explode(",", $fields);
		foreach($temp as $k => $v)
			$temp[$k] = trim($v);
		$this->fields = array_intersect($temp, $this->tableColumns);
		if(empty($this->fields)) throw new Exception("Brak poprawnych nazw pól");
		
		return $this;
	}
/* Metoda setFields
 * ustawia nazwy pól które zostaną użyte w zapytaniu
 * jak parametr należy podać nazwy pól które zostaną wykorzystane w zapytaniu
 */
	public function setValues($values)
	{
		$temp = explode(",", $values);
		foreach($temp as $k => $v)
			if(strtoupper(trim(urldecode($v))) == "NULL")
				$temp[$k] = "NULL";
			else
				$temp[$k] = "'".trim(urldecode($v))."'";

		if($this->type == "INSERT")
		{
			if($this->values != null) $this->values .= ", ";
			$this->values .= "(".implode(",", $temp).")";
		}
		else
			$this->values = $temp;
		if(empty($this->values)) throw new Exception("Brak poprawnych wartości");
		
		return $this;
	}
/* Metoda where
 * ustawia warunki jakie musi spełnić wynik zapytania
 * jak parametr należy podać warunek (pole = wartość)
 */
	public function where($where)
	{
		if(empty($this->where)) $this->where = $where;
		else $this->andWhere($where);
		
		return $this;
	}
/* Metoda andWhere
 * służy do ustawiania dodatkowych warunków
 */
	public function andWhere($where)
	{
		if(!empty($this->where)) $this->where .= " AND ".$where;
		else $this->where($where);
		
		return $this;
	}
/* Metoda orWhere
 * służy do ustawiania dodatkowych warunków
 */
	public function orWhere($where)
	{
		if(!empty($this->where)) $this->where .= " OR ".$where;
		else $this->where($where);
		
		return $this;
	}
/* Metoda order
 * służy do ustawiania sortowania
 */
	public function order($by = 1, $direction = "ASC")
	{
		$direction = strtoupper($direction);
		if($direction != "ASC" && $direction != "DESC") $direction = "ASC";
		if(is_array($by)) implode(",", array_intersect($by, $this->tableColumns));
		elseif(is_int($by) && $by > count($this->tableColumns)) $by = 1;
		elseif(is_string($by) && !in_array($by, $this->tableColumns)) $by = 1;
		$this->order = " ORDER BY ".$by." ".$direction;
		
		return $this;
	}
/* Metoda group
 * służy do ustawiania grupowania
 */
	public function group($by = 1)
	{
		if(is_int($by) && $by > count($this->tableColumns)) $by = 1;
		if(is_string($by) && !in_array($by, $this->tableColumns)) $by = 1;
		$this->group = " GROUP BY ".$by;
		
		return $this;
	}	
/* Metoda limit
 * metoda służąca do ustawiania limitu i offsetu
 */
	public function limit($limit, $offset = false)
	{
		$this->limit = " LIMIT ".($offset ? $offset.", " : null).$limit;
		
		return $this;
	}
/* Metoda count
 * metoda służąca do odczytywania ilosci rekordów w tabeli
 */
	public function count($num = 1)
	{
		$this->fields[] = "COUNT(".$num.")";
		
		return $this;
	}
/* Metoda sum
 * metoda służąca do sumowania wartości rekordów w tabeli
 */
	public function sum($num = 1)
	{
		$this->fields[] = "SUM(".$num.")";
		
		return $this;
	}
/* metoda służąca do wykonania zapytania
 *
 */
	public function execute($type = MYSQLI_BOTH)
	{
		if($this->squery == null)
			$this->squery = $this->prepareQuery();
		$this->queryResult = parent::query($this->squery);
		//$this->returnResult = $this->queryResult->fetch_all(MYSQLI_BOTH);
		if($this->type == "SELECT")
		{
			if($this->queryResult->num_rows > 0)
				while($result = $this->queryResult->fetch_array($type))
					$this->returnResult[] = $result;
			return $this->queryResult->num_rows;
		}
		else return $this->affected_rows;
	}
	// Pobranie wyników wykonanego zapytania
	public function getResult()
	{
		if(!empty($this->returnResult))
		{
			$tmp = current($this->returnResult);
			next($this->returnResult);
			return $tmp;
		}
		else return null;
	}
	// Ręczne wpisanie zapytania
	public function query($q)
	{
		$this->squery = $q;
		
		return $this;
	}
	// Ostatnio wpisane zapytanie
	public function getLastQuery()
	{
		return $this->squery;
	}
	// Przygotowanie zapytania
	protected function prepareQuery()
	{
		switch($this->type)
		{
			case "DELETE":
				$query = "DELETE FROM ".$this->table." WHERE ".(empty($this->where) ? "1" : $this->where).$this->order.$this->limit;
				break;
			case "UPDATE":
				$this->combineUpdateValues();
				$query = "UPDATE ".$this->table." SET ".$this->set." WHERE ".(empty($this->where) ? "1" : $this->where).$this->order.$this->limit;
				break;
			case "INSERT":
				//if(count($this->fields) == count($this->values))
				$query = "INSERT INTO ".$this->table."(".implode(",", $this->fields).") VALUES".$this->values;
				break;
			case "SELECT":
			default:
				$query = "SELECT ".(empty($this->fields) ? "*" : (implode(",", $this->fields)))." FROM ".$this->table.(isset($this->table2) ? ", ".$this->table2 : null)." WHERE ".(empty($this->where) ? "1" : $this->where).$this->group.$this->order.$this->limit;
				break;
		}
		if(constant("ENVIROMENT") == "debug") echo $query;
		return $query;
	}
/*	Metoda getTableColumns
 *	Pobiera listę kolumn znajdujących się w tabeli
 *	W przypadku braku ustawionej nazwy tabeli wyrzuca wyjątek
 */
	protected function getTableColumns($id = null)
	{
		$table = "table".$id;
		if($this->$table == "") throw new Exception("Nieprawidłowa nazwa tabeli", 405);
		$query = "SHOW COLUMNS FROM ".$this->$table;
		$result = parent::query($query);
		if($result->num_rows == 0) throw new Exception("Brak kolumn w podanej tabeli", 406);
		while($data = $result->fetch_row())
			$this->tableColumns[] = $data[0];
	}
	// Połączenie wartości z nazwami pól
	protected function combineUpdateValues()
	{
		$tmp = array_combine($this->fields, $this->values);
		foreach($tmp as $k => $v)
		{
			$this->set .= $k."=";
			if(is_int($v)) $this->set .= $v.", ";
			elseif(is_float($v)) $this->set .= $v.", ";
			elseif(is_string($v) && !empty($v)) $this->set .= $v.", ";
			elseif(empty($v) && !is_numeric($v)) $this->set .= "NULL, ";
		}
		$this->set = trim($this->set, ", ");
	}
 }
?>